//
// "$Id: Fl_Table.cxx 10104 2014-02-16 08:36:34Z greg.ercolano $"
//
// Fl_Table -- A table widget
//
// Copyright 2002 by Greg Ercolano.
// Copyright (c) 2004 O'ksi'D
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file.  If this
// file is missing or damaged, see the license at:
//
//     http://www.fltk.org/COPYING.php
//
// Please report all bugs and problems on the following page:
//
//     http://www.fltk.org/str.php
//

#include <stdio.h>		// fprintf
#include "fl_draw.H"
#include "Fl_Table.H"

#if defined(USE_UTF8) && ( defined(MICROSOFT) || defined(LINUX) )
#include "fl_utf8.H"	// currently only Windows and Linux
#endif

// Scroll display so 'row' is at top
void Fl_Table::row_position(int row)
{
	if ( _row_position == row ) return;		// OPTIMIZATION: no change? avoid redraw
	if ( row < 0 ) row = 0;
	else if ( row >= rows() ) row = rows() - 1;
	if ( table_h <= tih ) return;			// don't scroll if table smaller than window
	double newtop = row_scroll_position(row);
	if ( newtop > vscrollbar->maximum() ) {
		newtop = vscrollbar->maximum();
	}
	vscrollbar->Fl_Slider::value(newtop);
	table_scrolled();
	redraw();
	_row_position = row;	// HACK: override what table_scrolled() came up with
}

// Scroll display so 'col' is at left
void Fl_Table::col_position(int col)
{
	if ( _col_position == col ) return;	// OPTIMIZATION: no change? avoid redraw
	if ( col < 0 ) col = 0;
	else if ( col >= cols() ) col = cols() - 1;
	if ( table_w <= tiw ) return;		// don't scroll if table smaller than window
	double newleft = col_scroll_position(col);
	if ( newleft > hscrollbar->maximum() ) {
		newleft = hscrollbar->maximum();
	}
	hscrollbar->Fl_Slider::value(newleft);
	table_scrolled();
	redraw();
	_col_position = col;	// HACK: override what table_scrolled() came up with
}

// Find scroll position of a row (in pixels)
long Fl_Table::row_scroll_position(int row)
{
	int startrow = 0;
	long scroll = 0;
	// OPTIMIZATION:
	//     Attempt to use precomputed row scroll position
	//
	if ( toprow_scrollpos != -1 && row >= toprow ) {
		scroll = toprow_scrollpos;
		startrow = toprow;
	}
	for ( int t=startrow; t<row; t++ ) {
		scroll += row_height(t);
	}
	return(scroll);
}

// Find scroll position of a column (in pixels)
long Fl_Table::col_scroll_position(int col)
{
	int startcol = 0;
	long scroll = 0;
	// OPTIMIZATION:
	//     Attempt to use precomputed row scroll position
	//
	if ( leftcol_scrollpos != -1 && col >= leftcol ) {
		scroll = leftcol_scrollpos;
		startcol = leftcol;
	}
	for ( int t=startcol; t<col; t++ ) {
		scroll += col_width(t);
	}
	return(scroll);
}

// Ctor
Fl_Table::Fl_Table(int X, int Y, int W, int H, const char *l) : Fl_Group(X,Y,W,H,l)
{
	_rows             = 0;
	_cols             = 0;
	_row_header_w     = 40;
	_col_header_h     = 18;
	_row_header       = 0;
	_col_header       = 0;
	_row_header_color = color();
	_col_header_color = color();
	_row_resize       = 0;
	_col_resize       = 0;
	_row_resize_min   = 1;
	_col_resize_min   = 1;
	_redraw_toprow    = -1;
	_redraw_botrow    = -1;
	_redraw_leftcol   = -1;
	_redraw_rightcol  = -1;
	table_w           = 0;
	table_h           = 0;
	toprow            = 0;
	botrow            = 0;
	leftcol           = 0;
	rightcol          = 0;
	toprow_scrollpos  = -1;
	leftcol_scrollpos = -1;
	_last_cursor      = FL_CURSOR_DEFAULT;
	_resizing_col     = -1;
	_resizing_row     = -1;
	_dragging_x       = -1;
	_dragging_y       = -1;
	_last_row         = -1;
	_auto_drag        = 0;
	current_col	    = -1;
	current_row       = -1;
	select_row        = -1;
	select_col        = -1;
#if FLTK_ABI_VERSION >= 10301
	_scrollbar_size   = 0;
#endif
#if FLTK_ABI_VERSION >= 10303
	flags_            = 0;	// TABCELLNAV off
#endif
	box(FL_THIN_DOWN_FRAME);

	vscrollbar = new Fl_Scrollbar(x()+w()-Fl::scrollbar_size(), y(),
	                              Fl::scrollbar_size(), h()-Fl::scrollbar_size());
	vscrollbar->type(FL_VERTICAL);
	vscrollbar->callback(scroll_cb, (void*)this);

	hscrollbar = new Fl_Scrollbar(x(), y()+h()-Fl::scrollbar_size(),
	                              w(), Fl::scrollbar_size());
	hscrollbar->type(FL_HORIZONTAL);
	hscrollbar->callback(scroll_cb, (void*)this);

	table = new Fl_Scroll(x(), y(), w(), h());
	table->box(FL_NO_BOX);
	table->type(0);		// don't show Fl_Scroll's scrollbars -- use our own
	table->hide();		// hide unless children are present
	table->end();

	table_resized();
	redraw();

	Fl_Group::end();		// end the group's begin()

	table->begin();		// leave with fltk children getting added to the scroll
}

// Dtor
Fl_Table::~Fl_Table()
{
	// The parent Fl_Group takes care of destroying scrollbars
}

// Set height of a row
void Fl_Table::row_height(int row, int height)
{
	if ( row < 0 ) return;
	if ( row < (int)_rowheights.size() && _rowheights[row] == height ) {
		return;		// OPTIMIZATION: no change? avoid redraw
	}
	// Add row heights, even if none yet
	int now_size = (int)_rowheights.size();
	if ( row >= now_size ) {
		_rowheights.size(row);
		while (now_size < row)
			_rowheights[now_size++] = height;
	}
	_rowheights[row] = height;
	table_resized();
	if ( row <= botrow ) {	// OPTIMIZATION: only redraw if onscreen or above screen
		redraw();
	}
	// ROW RESIZE CALLBACK
	if ( Fl_Widget::callback() && when() & FL_WHEN_CHANGED ) {
		do_callback(CONTEXT_RC_RESIZE, row, 0);
	}
}

// Set width of a column
void Fl_Table::col_width(int col, int width)
{
	if ( col < 0 ) return;
	if ( col < (int)_colwidths.size() && _colwidths[col] == width ) {
		return;			// OPTIMIZATION: no change? avoid redraw
	}
	// Add column widths, even if none yet
	int now_size = (int)_colwidths.size();
	if ( col >= now_size ) {
		_colwidths.size(col+1);
		while (now_size < col) {
			_colwidths[now_size++] = width;
		}
	}
	_colwidths[col] = width;
	table_resized();
	if ( col <= rightcol ) {	// OPTIMIZATION: only redraw if onscreen or to the left
		redraw();
	}
	// COLUMN RESIZE CALLBACK
	if ( Fl::focus() == this && Fl_Widget::callback() && when() & FL_WHEN_CHANGED ) {
		do_callback(CONTEXT_RC_RESIZE, 0, col);
	}
}

// Return row/col clamped to reality
int Fl_Table::row_col_clamp(TableContext context, int &R, int &C)
{
	int clamped = 0;
	if ( R < 0 ) {
		R = 0;
		clamped = 1;
	}
	if ( C < 0 ) {
		C = 0;
		clamped = 1;
	}
	switch ( context ) {
	case CONTEXT_COL_HEADER:
		// Allow col headers to draw even if no rows
		if ( R >= _rows && R != 0 ) {
			R = _rows - 1;
			clamped = 1;
		}
		break;

	case CONTEXT_ROW_HEADER:
		// Allow row headers to draw even if no columns
		if ( C >= _cols && C != 0 ) {
			C = _cols - 1;
			clamped = 1;
		}
		break;

	case CONTEXT_CELL:
	default:
		// CLAMP R/C TO _rows/_cols
		if ( R >= _rows ) {
			R = _rows - 1;
			clamped = 1;
		}
		if ( C >= _cols ) {
			C = _cols - 1;
			clamped = 1;
		}
		break;
	}
	return(clamped);
}

// Return bounding region for given context
void Fl_Table::get_bounds(TableContext context, int &X, int &Y, int &W, int &H)
{
	switch ( context ) {
	case CONTEXT_COL_HEADER:
		// Column header clipping.
		X = tox;
		Y = wiy;
		W = tow;
		H = col_header_height();
		return;

	case CONTEXT_ROW_HEADER:
		// Row header clipping.
		X = wix;
		Y = toy;
		W = row_header_width();
		H = toh;
		return;

	case CONTEXT_TABLE:
		// Table inner dimensions
		X = tix;
		Y = tiy;
		W = tiw;
		H = tih;
		return;

		// TODO: Add other contexts..
	default:
		fprintf(stderr, "Fl_Table::get_bounds(): context %d unimplemented\n", (int)context);
		return;
	}
	//NOTREACHED
}

// Find row/col beneath cursor
//
//    Returns R/C and context.
//    Also returns resizeflag, if mouse is hovered over a resize boundary.
//
Fl_Table::TableContext Fl_Table::cursor2rowcol(int &R, int &C, ResizeFlag &resizeflag)
{
	// return values
	R = C = 0;
	resizeflag = RESIZE_NONE;
	// Row header?
	int X, Y, W, H;
	if ( row_header() ) {
		// Inside a row heading?
		get_bounds(CONTEXT_ROW_HEADER, X, Y, W, H);
		if ( Fl::event_inside(X, Y, W, H) ) {
			// Scan visible rows until found
			for ( R = toprow; R <= botrow; R++ ) {
				find_cell(CONTEXT_ROW_HEADER, R, 0, X, Y, W, H);
				if ( Fl::event_y() >= Y && Fl::event_y() < (Y+H) ) {
					// Found row?
					//     If cursor over resize boundary, and resize enabled,
					//     enable the appropriate resize flag.
					//
					if ( row_resize() ) {
						if ( Fl::event_y() <= (Y+3-0) ) {
							resizeflag = RESIZE_ROW_ABOVE;
						}
						if ( Fl::event_y() >= (Y+H-3) ) {
							resizeflag = RESIZE_ROW_BELOW;
						}
					}
					return(CONTEXT_ROW_HEADER);
				}
			}
			// Must be in row header dead zone
			return(CONTEXT_NONE);
		}
	}
	// Column header?
	if ( col_header() ) {
		// Inside a column heading?
		get_bounds(CONTEXT_COL_HEADER, X, Y, W, H);
		if ( Fl::event_inside(X, Y, W, H) ) {
			// Scan visible columns until found
			for ( C = leftcol; C <= rightcol; C++ ) {
				find_cell(CONTEXT_COL_HEADER, 0, C, X, Y, W, H);
				if ( Fl::event_x() >= X && Fl::event_x() < (X+W) ) {
					// Found column?
					//     If cursor over resize boundary, and resize enabled,
					//     enable the appropriate resize flag.
					//
					if ( col_resize() ) {
						if ( Fl::event_x() <= (X+3-0) ) {
							resizeflag = RESIZE_COL_LEFT;
						}
						if ( Fl::event_x() >= (X+W-3) ) {
							resizeflag = RESIZE_COL_RIGHT;
						}
					}
					return(CONTEXT_COL_HEADER);
				}
			}
			// Must be in column header dead zone
			return(CONTEXT_NONE);
		}
	}
	// Mouse somewhere in table?
	//     Scan visible r/c's until we find it.
	//
	if ( Fl::event_inside(tox, toy, tow, toh) ) {
		for ( R = toprow; R <= botrow; R++ ) {
			find_cell(CONTEXT_CELL, R, C, X, Y, W, H);
			if ( Fl::event_y() < Y ) break;		// OPT: thanks lars
			if ( Fl::event_y() >= (Y+H) ) continue;	// OPT: " "
			for ( C = leftcol; C <= rightcol; C++ ) {
				find_cell(CONTEXT_CELL, R, C, X, Y, W, H);
				if ( Fl::event_inside(X, Y, W, H) ) {
					return(CONTEXT_CELL);			// found it
				}
			}
		}
		// Must be in a dead zone of the table
		R = C = 0;
		return(CONTEXT_TABLE);
	}
	// Somewhere else
	return(CONTEXT_NONE);
}

// Find X/Y/W/H for cell at R/C
//     If R or C are out of range, returns -1
//     with X/Y/W/H set to zero.
//
int Fl_Table::find_cell(TableContext context, int R, int C, int &X, int &Y, int &W, int &H)
{
	if ( row_col_clamp(context, R, C) ) {		// row or col out of range? error
		X=Y=W=H=0;
		return(-1);
	}
	X = col_scroll_position(C) - hscrollbar->value() + tix;
	Y = row_scroll_position(R) - vscrollbar->value() + tiy;
	W = col_width(C);
	H = row_height(R);

	switch ( context ) {
	case CONTEXT_COL_HEADER:
		Y = wiy;
		H = col_header_height();
		return(0);

	case CONTEXT_ROW_HEADER:
		X = wix;
		W = row_header_width();
		return(0);

	case CONTEXT_CELL:
		return(0);

	case CONTEXT_TABLE:
		return(0);

		// TODO -- HANDLE OTHER CONTEXTS
	default:
		fprintf(stderr, "Fl_Table::find_cell: unknown context %d\n", (int)context);
		return(-1);
	}
	//NOTREACHED
}

// Enable automatic scroll-selection
void Fl_Table::_start_auto_drag()
{
	if (_auto_drag) return;
	_auto_drag = 1;
	Fl::add_timeout(0.3, _auto_drag_cb2, this);
}

// Disable automatic scroll-selection
void Fl_Table::_stop_auto_drag()
{
	if (!_auto_drag) return;
	Fl::remove_timeout(_auto_drag_cb2, this);
	_auto_drag = 0;
}

void Fl_Table::_auto_drag_cb2(void *d)
{
	((Fl_Table*)d)->_auto_drag_cb();
}

// Handle automatic scroll-selection if mouse selection dragged off table edge
void Fl_Table::_auto_drag_cb()
{
	int lx = Fl::e_x;
	int ly = Fl::e_y;
	if (_selecting == CONTEXT_COL_HEADER) {
		ly = y() + col_header_height();
	} else if (_selecting == CONTEXT_ROW_HEADER) {
		lx = x() + row_header_width();
	}
	if (lx > x() + w() - 20) {
		Fl::e_x = x() + w() - 20;
		if (hscrollbar->visible())
			((Fl_Slider*)hscrollbar)->value(hscrollbar->clamp(hscrollbar->value() + 30));
		hscrollbar->do_callback();
		_dragging_x = Fl::e_x - 30;
	} else if (lx < (x() + row_header_width())) {
		Fl::e_x = x() + row_header_width() + 1;
		if (hscrollbar->visible()) {
			((Fl_Slider*)hscrollbar)->value(hscrollbar->clamp(hscrollbar->value() - 30));
		}
		hscrollbar->do_callback();
		_dragging_x = Fl::e_x + 30;
	}
	if (ly > y() + h() - 20) {
		Fl::e_y = y() + h() - 20;
		if (vscrollbar->visible()) {
			((Fl_Slider*)vscrollbar)->value(vscrollbar->clamp(vscrollbar->value() + 30));
		}
		vscrollbar->do_callback();
		_dragging_y = Fl::e_y - 30;
	} else if (ly < (y() + col_header_height())) {
		Fl::e_y = y() + col_header_height() + 1;
		if (vscrollbar->visible()) {
			((Fl_Slider*)vscrollbar)->value(vscrollbar->clamp(vscrollbar->value() - 30));
		}
		vscrollbar->do_callback();
		_dragging_y = Fl::e_y + 30;
	}
	_auto_drag = 2;
	handle(FL_DRAG);
	_auto_drag = 1;
	Fl::e_x = lx;
	Fl::e_y = ly;
	Fl::check();
	Fl::flush();
	if (Fl::event_buttons() && _auto_drag) {
		Fl::add_timeout(0.05, _auto_drag_cb2, this);
	}
}

// Recalculate the window dimensions
void Fl_Table::recalc_dimensions()
{
	// Recalc to* (Table Outer), ti* (Table Inner), wi* ( Widget Inner)
	wix = ( x() + Fl::box_dx(box()));
	tox = wix;
	tix = tox + Fl::box_dx(table->box());
	wiy = ( y() + Fl::box_dy(box()));
	toy = wiy;
	tiy = toy + Fl::box_dy(table->box());
	wiw = ( w() - Fl::box_dw(box()));
	tow = wiw;
	tiw = tow - Fl::box_dw(table->box());
	wih = ( h() - Fl::box_dh(box()));
	toh = wih;
	tih = toh - Fl::box_dh(table->box());
	// Trim window if headers enabled
	if ( col_header() ) {
		tiy += col_header_height();
		toy += col_header_height();
		tih -= col_header_height();
		toh -= col_header_height();
	}
	if ( row_header() ) {
		tix += row_header_width();
		tox += row_header_width();
		tiw -= row_header_width();
		tow -= row_header_width();
	}
	// Make scroll bars disappear if window large enough
	{
		// First pass: can hide via window size?
		int hidev = (table_h <= tih);
		int hideh = (table_w <= tiw);
#if FLTK_ABI_VERSION >= 10301
		// NEW
		int scrollsize = _scrollbar_size ? _scrollbar_size : Fl::scrollbar_size();
#else
		// OLD
		int scrollsize = Fl::scrollbar_size();
#endif
		// Second pass: Check for interference
		if ( !hideh & hidev ) {
			hidev = (( table_h - tih + scrollsize ) <= 0 );
		}
		if ( !hidev & hideh ) {
			hideh = (( table_w - tiw + scrollsize ) <= 0 );
		}
		// Determine scrollbar visibility, trim ti[xywh]/to[xywh]
		if ( hidev ) {
			vscrollbar->hide();
		} else {
			vscrollbar->show();
			tiw -= scrollsize;
			tow -= scrollsize;
		}
		if ( hideh ) {
			hscrollbar->hide();
		} else {
			hscrollbar->show();
			tih -= scrollsize;
			toh -= scrollsize;
		}
	}
	// Resize the child table
	table->resize(tox, toy, tow, toh);
	table->init_sizes();
}

// Recalculate internals after a scroll.
//
//    Call this if table has been scrolled or resized.
//    Does not handle redraw().
//    TODO: Assumes ti[xywh] has already been recalculated.
//
void Fl_Table::table_scrolled()
{
	// Find top row
	int y, row, voff = vscrollbar->value();
	for ( row=y=0; row < _rows; row++ ) {
		y += row_height(row);
		if ( y > voff ) {
			y -= row_height(row);
			break;
		}
	}
	_row_position = toprow = ( row >= _rows ) ? (row - 1) : row;
	toprow_scrollpos = y;		// OPTIMIZATION: save for later use
	// Find bottom row
	voff = vscrollbar->value() + tih;
	for ( ; row < _rows; row++ ) {
		y += row_height(row);
		if ( y >= voff ) {
			break;
		}
	}
	botrow = ( row >= _rows ) ? (row - 1) : row;
	// Left column
	int x, col, hoff = hscrollbar->value();
	for ( col=x=0; col < _cols; col++ ) {
		x += col_width(col);
		if ( x > hoff ) {
			x -= col_width(col);
			break;
		}
	}
	_col_position = leftcol = ( col >= _cols ) ? (col - 1) : col;
	leftcol_scrollpos = x;	// OPTIMIZATION: save for later use
	// Right column
	//    Work with data left over from leftcol calculation
	//
	hoff = hscrollbar->value() + tiw;
	for ( ; col < _cols; col++ ) {
		x += col_width(col);
		if ( x >= hoff ) {
			break;
		}
	}
	rightcol = ( col >= _cols ) ? (col - 1) : col;
	// First tell children to scroll
	draw_cell(CONTEXT_RC_RESIZE, 0,0,0,0,0,0);
}

// Table resized: recalc internal data
//    Call this whenever the window is resized.
//    Recalculates the scrollbar sizes.
//    Makes no assumptions about any pre-initialized data.
//
void Fl_Table::table_resized()
{
	table_h = row_scroll_position(rows());
	table_w = col_scroll_position(cols());
	recalc_dimensions();
	// Recalc scrollbar sizes
	//    Clamp scrollbar value() after a resize.
	//    Resize scrollbars to enforce a constant trough width after a window resize.
	//
	{
		// Vertical scrollbar
		float vscrolltab = ( table_h == 0 || tih > table_h ) ? 1 : (float)tih / table_h;
		float hscrolltab = ( table_w == 0 || tiw > table_w ) ? 1 : (float)tiw / table_w;
#if FLTK_ABI_VERSION >= 10301
		// NEW
		int scrollsize = _scrollbar_size ? _scrollbar_size : Fl::scrollbar_size();
#else
		// OLD
		int scrollsize = Fl::scrollbar_size();
#endif
		vscrollbar->bounds(0, table_h-tih);
		vscrollbar->precision(10);
		vscrollbar->slider_size(vscrolltab);
		vscrollbar->resize(wix+wiw-scrollsize, wiy,
		                   scrollsize,
		                   wih - ((hscrollbar->visible())?scrollsize:0));
		vscrollbar->Fl_Valuator::value(vscrollbar->clamp(vscrollbar->value()));
		// Horizontal scrollbar
		hscrollbar->bounds(0, table_w-tiw);
		hscrollbar->precision(10);
		hscrollbar->slider_size(hscrolltab);
		hscrollbar->resize(wix, wiy+wih-scrollsize,
		                   wiw - ((vscrollbar->visible())?scrollsize:0),
		                   scrollsize);
		hscrollbar->Fl_Valuator::value(hscrollbar->clamp(hscrollbar->value()));
	}

	// Tell FLTK child widgets were resized
	Fl_Group::init_sizes();

	// Recalc top/bot/left/right
	table_scrolled();

	// DO *NOT* REDRAW -- LEAVE THIS UP TO THE CALLER
	// redraw();
}

// Someone moved a scrollbar
void Fl_Table::scroll_cb(Fl_Widget*w, void *data)
{
	Fl_Table *o = (Fl_Table*)data;
	o->recalc_dimensions();	// recalc tix, tiy, etc.
	o->table_scrolled();
	o->redraw();
}

// Set number of rows
void Fl_Table::rows(int val)
{
	int oldrows = _rows;
	_rows = val;
	{
		int default_h = ( _rowheights.size() > 0 ) ? _rowheights.back() : 25;
		int now_size = _rowheights.size();
		_rowheights.size(val);			// enlarge or shrink as needed
		while ( now_size < val ) {
			_rowheights[now_size++] = default_h;	// fill new
		}
	}
	table_resized();

	// OPTIMIZATION: redraw only if change is visible.
	if ( val >= oldrows && oldrows > botrow ) {
		// NO REDRAW
	} else {
		redraw();
	}
}

// Set number of cols
void Fl_Table::cols(int val)
{
	_cols = val;
	{
		int default_w = ( _colwidths.size() > 0 ) ? _colwidths[_colwidths.size()-1] : 80;
		int now_size = _colwidths.size();
		_colwidths.size(val);			// enlarge or shrink as needed
		while ( now_size < val ) {
			_colwidths[now_size++] = default_w;	// fill new
		}
	}
	table_resized();
	redraw();
}

// Change mouse cursor to different type
void Fl_Table::change_cursor(Fl_Cursor newcursor)
{
	if ( newcursor != _last_cursor ) {
		fl_cursor(newcursor, FL_BLACK, FL_WHITE);
		_last_cursor = newcursor;
	}
}

void Fl_Table::damage_zone(int r1, int c1, int r2, int c2, int r3, int c3)
{
	int R1 = r1, C1 = c1;
	int R2 = r2, C2 = c2;
	if (r1 > R2) R2 = r1;
	if (r2 < R1) R1 = r2;
	if (r3 > R2) R2 = r3;
	if (r3 < R1) R1 = r3;
	if (c1 > C2) C2 = c1;
	if (c2 < C1) C1 = c2;
	if (c3 > C2) C2 = c3;
	if (c3 < C1) C1 = c3;
	if (R1 < 0) {
		if (R2 < 0) return;
		R1 = 0;
	}
	if (C1 < 0) {
		if (C2 < 0) return;
		C1 = 0;
	}
	if (R1 < toprow) R1 = toprow;
	if (R2 > botrow) R2 = botrow;
	if (C1 < leftcol) C1 = leftcol;
	if (C2 > rightcol) C2 = rightcol;
	redraw_range(R1, R2, C1, C2);
}

int Fl_Table::move_cursor(int R, int C, int shiftselect)
{
	if (select_row == -1) R++;
	if (select_col == -1) C++;
	R += select_row;
	C += select_col;
	if (R < 0) R = 0;
	if (R >= rows()) R = rows() - 1;
	if (C < 0) C = 0;
	if (C >= cols()) C = cols() - 1;
	if (R == select_row && C == select_col) return 0;
	damage_zone(current_row, current_col, select_row, select_col, R, C);
	select_row = R;
	select_col = C;
	if (!shiftselect || !Fl::event_state(FL_SHIFT)) {
		current_row = R;
		current_col = C;
	}
	if (R < toprow + 1 || R > botrow - 1) row_position(R);
	if (C < leftcol + 1 || C > rightcol - 1) col_position(C);
	return 1;
}

int Fl_Table::move_cursor(int R, int C)
{
	return move_cursor(R,C,1);
}

//#define DEBUG 1
#ifdef DEBUG
#include "names.h"
#define PRINTEVENT \
    fprintf(stderr,"Table %s: ** Event: %s --\n", (label()?label():"none"), fl_eventnames[event]);
#else
#define PRINTEVENT
#endif

// Handle FLTK events
int Fl_Table::handle(int event)
{
	PRINTEVENT;
	int ret = Fl_Group::handle(event);	// let FLTK group handle events first
	// Which row/column are we over?
	int R, C;  				// row/column being worked on
	ResizeFlag resizeflag;		// which resizing area are we over? (0=none)
	TableContext context = cursor2rowcol(R, C, resizeflag);
	if (ret) {
		if (Fl::event_inside(hscrollbar) || Fl::event_inside(vscrollbar)) return 1;
		if ( context != CONTEXT_ROW_HEADER &&		// mouse not in row header (STR#2742)
		     context != CONTEXT_COL_HEADER &&		// mouse not in col header (STR#2742)
		     Fl::focus() != this && 			// we don't have focus?
		     contains(Fl::focus())) {			// focus is a child?
			return 1;
		}
	}
	// Make snapshots of realtime event states *before* we service user's cb,
	// which may do things like post popup menus that return with unexpected button states.
	int _event_button = Fl::event_button();
	int _event_clicks = Fl::event_clicks();
	int _event_x      = Fl::event_x();
	int _event_y      = Fl::event_y();
	int _event_key    = Fl::event_key();
#if FLTK_ABI_VERSION >= 10303
	int _event_state  = Fl::event_state();
#endif
	Fl_Widget *_focus = Fl::focus();
	switch ( event ) {
	case FL_PUSH:
		// Single left-click on table? do user's callback with CONTEXT_TABLE
		if (_event_button == 1 && !_event_clicks) {
			if (_focus == this) {
				take_focus();
				do_callback(CONTEXT_TABLE, -1, -1);
				ret = 1;
			}
			damage_zone(current_row, current_col, select_row, select_col, R, C);
			if (context == CONTEXT_CELL) {
				current_row = select_row = R;
				current_col = select_col = C;
				_selecting = CONTEXT_CELL;
			} else {
				// Clear selection if not resizing row/col
				if ( !resizeflag ) {
					current_row = select_row = -1;
					current_col = select_col = -1;
				}
			}
		}
		// A click on table with user's callback defined?
		//     Need this for eg. right click to pop up a menu
		//
		if ( Fl_Widget::callback() &&		// callback defined?
		     resizeflag == RESIZE_NONE ) {		// not resizing?
			do_callback(context, R, C);		// do callback with context (cell, header, etc)
		}
		// Handle selection if handling a left-click
		//    Use snapshot of _event_button we made before servicing user's cb's
		//    to avoid checking realtime state of buttons which may have changed
		//    during the user's callbacks.
		//
		switch ( context ) {
		case CONTEXT_CELL:
			// FL_PUSH on a cell?
			ret = 1;				// express interest in FL_RELEASE
			break;

		case CONTEXT_NONE:
			// FL_PUSH on table corner?
			if ( _event_button == 1 && _event_x < x() + row_header_width()) {
				current_col = 0;
				select_col = cols() - 1;
				current_row = 0;
				select_row = rows() - 1;
				damage_zone(current_row, current_col, select_row, select_col);
				ret = 1;
			}
			break;

		case CONTEXT_COL_HEADER:
			// FL_PUSH on a column header?
			if ( _event_button == 1) {
				// Resizing? Handle it
				if ( resizeflag ) {
					// Start resize if left click on column border.
					//    "ret=1" ensures we get drag events from now on.
					//    (C-1) is used if mouse is over the left hand side
					//    of cell, so we resize the next column on the left.
					//
					_resizing_col = ( resizeflag & RESIZE_COL_LEFT ) ? C-1 : C;
					_resizing_row = -1;
					_dragging_x = _event_x;
					ret = 1;
				} else {
					// Not resizing? Select the column
					if ( Fl::focus() != this && contains(Fl::focus()) ) return 0;	// STR #3018 - item 1
					current_col = select_col = C;
					current_row = 0;
					select_row = rows() - 1;
					_selecting = CONTEXT_COL_HEADER;
					damage_zone(current_row, current_col, select_row, select_col);
					ret = 1;
				}
			}
			break;

		case CONTEXT_ROW_HEADER:
			// FL_PUSH on a row header?
			if ( _event_button == 1 ) {
				// Resizing? Handle it
				if ( resizeflag ) {
					// Start resize if left mouse clicked on row border.
					//    "ret = 1" ensures we get drag events from now on.
					//    (R-1) is used if mouse is over the top of the cell,
					//    so that we resize the row above.
					//
					_resizing_row = ( resizeflag & RESIZE_ROW_ABOVE ) ? R-1 : R;
					_resizing_col = -1;
					_dragging_y = _event_y;
					ret = 1;
				} else {
					// Not resizing? Select the row
					if ( Fl::focus() != this && contains(Fl::focus()) ) return 0;	// STR #3018 - item 1
					current_row = select_row = R;
					current_col = 0;
					select_col = cols() - 1;
					_selecting = CONTEXT_ROW_HEADER;
					damage_zone(current_row, current_col, select_row, select_col);
					ret = 1;
				}
			}
			break;

		default:
			ret = 0;		// express disinterest
			break;
		}
		_last_row = R;
		break;

	case FL_DRAG:
		if (_auto_drag == 1) {
			ret = 1;
			break;
		}
		if ( _resizing_col > -1 ) {
			// Dragging column?
			//
			//    Let user drag even /outside/ the row/col widget.
			//    Don't allow column width smaller than 1.
			//    Continue to show FL_CURSOR_WE at all times during drag.
			//
			int offset = _dragging_x - _event_x;
			int new_w = col_width(_resizing_col) - offset;
			if ( new_w < _col_resize_min ) new_w = _col_resize_min;
			col_width(_resizing_col, new_w);
			_dragging_x = _event_x;
			table_resized();
			redraw();
			change_cursor(FL_CURSOR_WE);
			ret = 1;
			if ( Fl_Widget::callback() && when() & FL_WHEN_CHANGED ) {
				do_callback(CONTEXT_RC_RESIZE, R, C);
			}
		} else if ( _resizing_row > -1 ) {
			// Dragging row?
			//
			//    Let user drag even /outside/ the row/col widget.
			//    Don't allow row width smaller than 1.
			//    Continue to show FL_CURSOR_NS at all times during drag.
			//
			int offset = _dragging_y - _event_y;
			int new_h = row_height(_resizing_row) - offset;
			if ( new_h < _row_resize_min ) new_h = _row_resize_min;
			row_height(_resizing_row, new_h);
			_dragging_y = _event_y;
			table_resized();
			redraw();
			change_cursor(FL_CURSOR_NS);
			ret = 1;
			if ( Fl_Widget::callback() && when() & FL_WHEN_CHANGED ) {
				do_callback(CONTEXT_RC_RESIZE, R, C);
			}
		} else {
			if (_event_button == 1 &&
			    _selecting == CONTEXT_CELL &&
			    context == CONTEXT_CELL) {
				// Dragging a cell selection?
				if ( _event_clicks ) break;			// STR #3018 - item 2
				if (select_row != R || select_col != C) {
					damage_zone(current_row, current_col, select_row, select_col, R, C);
				}
				select_row = R;
				select_col = C;
				ret = 1;
			} else if (_event_button == 1 &&
			           _selecting == CONTEXT_ROW_HEADER &&
			           context & (CONTEXT_ROW_HEADER|CONTEXT_COL_HEADER|CONTEXT_CELL)) {
				if (select_row != R) {
					damage_zone(current_row, current_col, select_row, select_col, R, C);
				}
				select_row = R;
				ret = 1;
			} else if (_event_button == 1 &&
			           _selecting == CONTEXT_COL_HEADER
			           && context & (CONTEXT_ROW_HEADER|CONTEXT_COL_HEADER|CONTEXT_CELL)) {
				if (select_col != C) {
					damage_zone(current_row, current_col, select_row, select_col, R, C);
				}
				select_col = C;
				ret = 1;
			}
		}
		// Enable autodrag if not resizing, and mouse has moved off table edge
		if ( _resizing_row < 0 && _resizing_col < 0 && _auto_drag == 0 &&
		     ( _event_x > x() + w() - 20 ||
		       _event_x < x() + row_header_width() ||
		       _event_y > y() + h() - 20 ||
		       _event_y < y() + col_header_height()
		     ) ) {
			_start_auto_drag();
		}
		break;

	case FL_RELEASE:
		_stop_auto_drag();
		switch ( context ) {
		case CONTEXT_ROW_HEADER:		// release on row header
		case CONTEXT_COL_HEADER:		// release on col header
		case CONTEXT_CELL:			// release on a cell
		case CONTEXT_TABLE:			// release on dead zone
			if ( _resizing_col == -1 &&		// not resizing a column
			     _resizing_row == -1 &&		// not resizing a row
			     Fl_Widget::callback() && 		// callback defined
			     when() & FL_WHEN_RELEASE && 	// on button release
			     _last_row == R ) {		// release on same row PUSHed?
				// Need this for eg. left clicking on a cell to select it
				do_callback(context, R, C);
			}
			break;

		default:
			break;
		}
		if ( _event_button == 1 ) {
			change_cursor(FL_CURSOR_DEFAULT);
			_resizing_col = -1;
			_resizing_row = -1;
			ret = 1;
		}
		break;

	case FL_MOVE:
		if ( context == CONTEXT_COL_HEADER && 		// in column header?
		     resizeflag ) {				// resize + near boundary?
			change_cursor(FL_CURSOR_WE);			// show resize cursor
		} else if ( context == CONTEXT_ROW_HEADER && 	// in row header?
		            resizeflag ) {				// resize + near boundary?
			change_cursor(FL_CURSOR_NS);			// show resize cursor
		} else {
			change_cursor(FL_CURSOR_DEFAULT);		// normal cursor
		}
		ret = 1;
		break;

	case FL_ENTER:		// See FLTK event docs on the FL_ENTER widget
		if (!ret) take_focus();
		ret = 1;
		//FALLTHROUGH

	case FL_LEAVE:		// We want to track the mouse if resizing is allowed.
		if ( resizeflag ) {
			ret = 1;
		}
		if ( event == FL_LEAVE ) {
			_stop_auto_drag();
			change_cursor(FL_CURSOR_DEFAULT);
		}
		break;

	case FL_FOCUS:
		Fl::focus(this);
		//FALLTHROUGH

	case FL_UNFOCUS:
		_stop_auto_drag();
		ret = 1;
		break;

	case FL_KEYBOARD: {
		ret = 0;
		int is_row = select_row;
		int is_col = select_col;
		switch(_event_key) {
		case FL_Home:
			ret = move_cursor(0, -1000000);
			break;
		case FL_End:
			ret = move_cursor(0, 1000000);
			break;
		case FL_Page_Up:
			ret = move_cursor(-(botrow - toprow - 1), 0);
			break;
		case FL_Page_Down:
			ret = move_cursor(botrow - toprow - 1 , 0);
			break;
		case FL_Left:
			ret = move_cursor(0, -1);
			break;
		case FL_Right:
			ret = move_cursor(0, 1);
			break;
		case FL_Up:
			ret = move_cursor(-1, 0);
			break;
		case FL_Down:
			ret = move_cursor(1, 0);
			break;
		case FL_Tab:
#if FLTK_ABI_VERSION >= 10303
			if ( !tab_cell_nav() ) break;		// not navigating cells? let fltk handle it (STR#2862)
			if ( _event_state & FL_SHIFT ) {
				ret = move_cursor(0, -1, 0);	// shift-tab -> left
			} else {
				ret = move_cursor(0, 1, 0);		// tab -> right
			}
			break;
#else
			break;				// without tab_cell_nav(), Fl_Table should default to navigating widgets, not cells
#endif
		}
		if (ret && Fl::focus() != this) {
			do_callback(CONTEXT_TABLE, -1, -1);
			take_focus();
		}
		//if (!ret && Fl_Widget::callback() && when() & FL_WHEN_NOT_CHANGED  )
		if ( Fl_Widget::callback() &&
		     (
		             ( !ret && when() & FL_WHEN_NOT_CHANGED ) ||
		             ( is_row!= select_row || is_col!= select_col )
		     )
		   ) {
			do_callback(CONTEXT_CELL, select_row, select_col);
			//damage_zone(current_row, current_col, select_row, select_col);
			ret = 1;
		}
		break;
	}

	default:
		change_cursor(FL_CURSOR_DEFAULT);
		break;
	}
	return(ret);
}

// Resize FLTK override
//     Handle resize events if user resizes parent window.
//
void Fl_Table::resize(int X, int Y, int W, int H)
{
	// Tell group to resize, and recalc our own widget as well
	Fl_Group::resize(X, Y, W, H);
	table_resized();
	redraw();
}

// Draw a cell
void Fl_Table::_redraw_cell(TableContext context, int r, int c)
{
	if ( r < 0 || c < 0 ) return;
	int X,Y,W,H;
	find_cell(context, r, c, X, Y, W, H);	// find positions of cell
	draw_cell(context, r, c, X, Y, W, H);	// call users' function to draw it
}

/**
 See if the cell at row \p r and column \p c is selected.
 \returns 1 if the cell is selected, 0 if not.
 */
int Fl_Table::is_selected(int r, int c)
{
	int s_left, s_right, s_top, s_bottom;

	if (select_col > current_col) {
		s_left = current_col;
		s_right = select_col;
	} else {
		s_right = current_col;
		s_left = select_col;
	}
	if (select_row > current_row) {
		s_top = current_row;
		s_bottom = select_row;
	} else {
		s_bottom = current_row;
		s_top = select_row;
	}
	if (r >= s_top && r <= s_bottom && c >= s_left && c <= s_right) {
		return 1;
	}
	return 0;
}

/**
  Gets the region of cells selected (highlighted).

  \param[in] row_top   Returns the top row of selection area
  \param[in] col_left  Returns the left column of selection area
  \param[in] row_bot   Returns the bottom row of selection area
  \param[in] col_right Returns the right column of selection area
*/
void Fl_Table::get_selection(int& row_top, int& col_left, int& row_bot, int& col_right)
{
	if (select_col > current_col) {
		col_left  = current_col;
		col_right = select_col;
	} else {
		col_right = current_col;
		col_left  = select_col;
	}
	if (select_row > current_row) {
		row_top = current_row;
		row_bot = select_row;
	} else {
		row_bot = current_row;
		row_top = select_row;
	}
}

/**
  Sets the region of cells to be selected (highlighted).

  So for instance, set_selection(0,0,0,0) selects the top/left cell in the table.
  And set_selection(0,0,1,1) selects the four cells in rows 0 and 1, column 0 and 1.

  \param[in] row_top   Top row of selection area
  \param[in] col_left  Left column of selection area
  \param[in] row_bot   Bottom row of selection area
  \param[in] col_right Right column of selection area
*/
void Fl_Table::set_selection(int row_top, int col_left, int row_bot, int col_right)
{
	damage_zone(current_row, current_col, select_row, select_col);
	current_col = col_left;
	current_row = row_top;
	select_col  = col_right;
	select_row  = row_bot;
	damage_zone(current_row, current_col, select_row, select_col);
}

// Draw the entire Fl_Table
//    Override the draw() routine to draw the table.
//    Then tell the group to draw over us.
//
void Fl_Table::draw()
{
#if FLTK_ABI_VERSION >= 10301
	// NEW
	int scrollsize = _scrollbar_size ? _scrollbar_size : Fl::scrollbar_size();
#else
	// OLD
	int scrollsize = Fl::scrollbar_size();
#endif
	// Check if scrollbar size changed
	if ( ( vscrollbar && (scrollsize != vscrollbar->w()) ) ||
	     ( hscrollbar && (scrollsize != hscrollbar->h()) ) ) {
		// handle size change, min/max, table dim's, etc
		table_resized();
	}

	draw_cell(CONTEXT_STARTPAGE, 0, 0,	 	// let user's drawing routine
	          tix, tiy, tiw, tih);		// prep new page

	// Let fltk widgets draw themselves first. Do this after
	// draw_cell(CONTEXT_STARTPAGE) in case user moves widgets around.
	// Use window 'inner' clip to prevent drawing into table border.
	// (unfortunately this clips FLTK's border, so we must draw it explicity below)
	//
	fl_push_clip(wix, wiy, wiw, wih);
	{
		Fl_Group::draw();
	}
	fl_pop_clip();

	// Explicitly draw border around widget, if any
	draw_box(box(), x(), y(), w(), h(), color());

	// If Fl_Scroll 'table' is hidden, draw its box
	//    Do this after Fl_Group::draw() so we draw over scrollbars
	//    that leak around the border.
	//
	if ( ! table->visible() ) {
		if ( damage() & FL_DAMAGE_ALL || damage() & FL_DAMAGE_CHILD ) {
			draw_box(table->box(), tox, toy, tow, toh, table->color());
		}
	}
	// Clip all further drawing to the inner widget dimensions
	fl_push_clip(wix, wiy, wiw, wih);
	{
		// Only redraw a few cells?
		if ( ! ( damage() & FL_DAMAGE_ALL ) && _redraw_leftcol != -1 ) {
			fl_push_clip(tix, tiy, tiw, tih);
			for ( int c = _redraw_leftcol; c <= _redraw_rightcol; c++ ) {
				for ( int r = _redraw_toprow; r <= _redraw_botrow; r++ ) {
					_redraw_cell(CONTEXT_CELL, r, c);
				}
			}
			fl_pop_clip();
		}
		if ( damage() & FL_DAMAGE_ALL ) {
			int X,Y,W,H;
			// Draw row headers, if any
			if ( row_header() ) {
				get_bounds(CONTEXT_ROW_HEADER, X, Y, W, H);
				fl_push_clip(X,Y,W,H);
				for ( int r = toprow; r <= botrow; r++ ) {
					_redraw_cell(CONTEXT_ROW_HEADER, r, 0);
				}
				fl_pop_clip();
			}
			// Draw column headers, if any
			if ( col_header() ) {
				get_bounds(CONTEXT_COL_HEADER, X, Y, W, H);
				fl_push_clip(X,Y,W,H);
				for ( int c = leftcol; c <= rightcol; c++ ) {
					_redraw_cell(CONTEXT_COL_HEADER, 0, c);
				}
				fl_pop_clip();
			}
			// Draw all cells.
			//    This includes cells partially obscured off edges of table.
			//    No longer do this last; you might think it would be nice
			//    to draw over dead zones, but on redraws it flickers. Avoid
			//    drawing over deadzones; prevent deadzones by sizing columns.
			//
			fl_push_clip(tix, tiy, tiw, tih);
			{
				for ( int r = toprow; r <= botrow; r++ ) {
					for ( int c = leftcol; c <= rightcol; c++ ) {
						_redraw_cell(CONTEXT_CELL, r, c);
					}
				}
			}
			fl_pop_clip();
			// Draw little rectangle in corner of headers
			if ( row_header() && col_header() ) {
				fl_rectf(wix, wiy, row_header_width(), col_header_height(), color());
			}

			// Table has a boxtype? Close those few dead pixels
			if ( table->box() ) {
				if ( col_header() ) {
					fl_rectf(tox, wiy, Fl::box_dx(table->box()), col_header_height(), color());
				}
				if ( row_header() ) {
					fl_rectf(wix, toy, row_header_width(), Fl::box_dx(table->box()), color());
				}
			}

			// Table width smaller than window? Fill remainder with rectangle
			if ( table_w < tiw ) {
				fl_rectf(tix + table_w, tiy, tiw - table_w, tih, color());
				// Col header? fill that too
				if ( col_header() ) {
					fl_rectf(tix + table_w,
					         wiy,
					         // get that corner just right..
					         (tiw - table_w + Fl::box_dw(table->box()) -
					          Fl::box_dx(table->box())),
					         col_header_height(),
					         color());
				}
			}
			// Table height smaller than window? Fill remainder with rectangle
			if ( table_h < tih ) {
				fl_rectf(tix, tiy + table_h, tiw, tih - table_h, color());
				if ( row_header() ) {
					// NOTE:
					//     Careful with that lower corner; don't use tih; when eg.
					//     table->box(FL_THIN_UP_FRAME) and hscrollbar hidden,
					//     leaves a row of dead pixels.
					//
					fl_rectf(wix, tiy + table_h, row_header_width(),
					         (wiy+wih) - (tiy+table_h) -
					         ( hscrollbar->visible() ? scrollsize : 0),
					         color());
				}
			}
		}
		// Both scrollbars? Draw little box in lower right
		if ( vscrollbar->visible() && hscrollbar->visible() ) {
			fl_rectf(vscrollbar->x(), hscrollbar->y(),
			         vscrollbar->w(), hscrollbar->h(), color());
		}
		draw_cell(CONTEXT_ENDPAGE, 0, 0,		// let user's drawing
		          tix, tiy, tiw, tih);		// routines cleanup

		_redraw_leftcol = _redraw_rightcol = _redraw_toprow = _redraw_botrow = -1;
	}
	fl_pop_clip();
}

//
// End of "$Id: Fl_Table.cxx 10104 2014-02-16 08:36:34Z greg.ercolano $".
//
